// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include "coin2/exchange/base/order/subsystem_manager.h"

#include <map>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>

#include "coin2/exchange/base/order/archive.h"
#include "coin2/exchange/factory/order/system.h"

namespace coin2::exchange::base::order {

OrderSystemManager::OrderSystemManager(
    const di::ConstantInjection& ci, const OrderManagerConfig& config,
    LiveAsyncExecutor* live_executor, ArchiveSyncExecutor* archive_executor,
    coin2::exchange::base::symbology::ProductHolderManager* phm,
    coin2::feed::mgr::FeedSubsystemManager* fss, IOrderSubscriber* strategy,
    const std::string& strategy_name)
    : config_(config),
      live_executor_(live_executor),
      archive_executor_(archive_executor),
      product_holder_manager_(phm) {
  using coin2::exchange::factory::order::CreateOrderSystem;
  using coin2::exchange::factory::order::CreateSimOrderSystem;

  std::unordered_set<std::string> system_name_set;
  for (const auto& [system_name, system] : config_.exchanges()) {
    CHECK_THROW(system_name_set.count(system_name) == 0)
        << "system name should be unique!!";
    system_name_set.emplace(system_name);
    auto mea = MarketExchangeApi::FromString(system.mea());
    auto type = system.type();

    if (type == OrderSystemType::ORDER_SYSTEM_TYPE_UNSPECIFIED) {
      THROW() << "[OrderSystemManager] Please configure system type for "
              << system_name;
    } else if (type == OrderSystemType::ORDER_SYSTEM_TYPE_LIVE_OG) {
      CHECK_THROW(live_executor_ != nullptr)
          << "You didn't provide LiveAsyncExecutor";
      auto get_product_holder = [this](const IProduct& product,
                                       int64_t timestamp) {
        return this->product_holder_manager_->GetProductHolder(product,
                                                               timestamp);
      };
      BaseOrderSubsystem* ptr =
          CreateOrderSystem(system_name, mea, ci, system, live_executor,
                            strategy, get_product_holder);
      systems_.emplace_back(ptr);
      if (stats_manager_ == nullptr) {
        stats_manager_ = std::make_unique<StatsManager>(
            live_executor->StatsIoc(), live_executor->MainIoc(), this,
            strategy_name);
      }
      stats_manager_->RegisterBuilder(system_name, mea.String(),
                                      ptr->GetAccountRequest().owner());
    } else if (type == OrderSystemType::ORDER_SYSTEM_TYPE_ARCHIVE) {
      CHECK_THROW(archive_executor_ != nullptr)
          << "You didn't provide ArchiveSyncExecutor";
      systems_.push_back(std::make_unique<ArchiveOrderSystem>(
          system_name, mea, ci, system, archive_executor, strategy));
    } else if (type == OrderSystemType::ORDER_SYSTEM_TYPE_SIM_OG) {
      auto get_product_holder = [this](const IProduct& product,
                                       int64_t timestamp) {
        return this->product_holder_manager_->GetProductHolder(product,
                                                               timestamp);
      };
      systems_.emplace_back(CreateSimOrderSystem(
          system_name, mea, ci, system, archive_executor, fss, strategy, get_product_holder));
    } else {
      throw std::runtime_error("unexpected order system type");
    }
  }

  if (stats_manager_) {
    stats_manager_->Run();
    control_handler_ =
        std::make_unique<ControlHandler>(live_executor->MainIoc(), this);
  }
}

BaseOrderSubsystem* OrderSystemManager::GetUniqueSystemByName(
    const std::string& system_name) {
  for (auto& sys : systems_) {
    if (sys->system_name() == system_name) {
      return sys.get();
    }
  }
  throw std::runtime_error(
      "[OrderSystemManager] cannot find OrderSystem with system_name=" +
      system_name);
}

BaseOrderSubsystem* OrderSystemManager::GetUniqueSystem(
    const MarketExchangeApi& mea) {
  BaseOrderSubsystem* ret = nullptr;
  for (auto& system : systems_) {
    if (system->mea() == mea) {
      if (ret != nullptr) {
        throw std::runtime_error("multiple order systems with the same mea");
      } else {
        ret = system.get();
      }
    }
  }
  if (ret == nullptr) {
    throw std::runtime_error("no order system with the mea: " + mea.String());
  }
  return ret;
}

std::vector<const IProduct*> OrderSystemManager::GetProducts() const {
  std::vector<const IProduct*> vec;
  for (const auto& sys : systems_) {
    for (const auto& product : sys->products()) {
      vec.push_back(product);
    }
  }
  return vec;
}

void OrderSystemManager::DebugPrint() const {
  LOG(INFO) << "[OrderSystemManager]";
  if (live_executor_ != nullptr) {
    LOG(INFO) << "\t   live ass = <..>";
  } else {
    LOG(INFO) << "\t   live ass = nullptr";
  }
  if (archive_executor_ != nullptr) {
    LOG(INFO) << "\tarchive ass = <..>";
  } else {
    LOG(INFO) << "\tarchive ass = nullptr";
  }
}

void OrderSystemManager::GetHealthReport(int indent,
                                         std::string* report) const {
  // [HealthReport]
  //     [Order]
  //         - Huobi (OK)
  //         - Okex (STARTING, uptime=23h)
  //               a1 (..)
  for (int i = 0; i < indent * 4; i++) {
    *report += ' ';
  }
  *report += "[Order]\n";
  for (auto& system : systems_) {
    system->GetHealthReport(indent + 1, report);
  }
}

void OrderSystemManager::OnEvent(ControlType uri) {
  for (auto& sys : systems_) {
    switch (uri) {
      case ControlType::EnableRawLog:
        sys->SetRawOGLog(true);
        break;
      case ControlType::DisableRawLog:
        sys->SetRawOGLog(false);
        break;
      default:
        break;
    }
  }
}

void OrderSystemManager::OnGenricReport() {
  if (stats_manager_ == nullptr) {
    return;
  }
  for (auto& sys : systems_) {
    Info info;
    info.stats_info = sys->GetStatsInfo();
    info.rtt_info = sys->GetRttInfo();
    info.ws_reset_num = sys->GetWsResetNum();
    info.req_info = sys->GetRequestInfo();
    stats_manager_->GenericReport(sys->system_name(), sys->mea().String(), info);
    // report to prometheus
    sys->GetApiEndPointStats(info.req_info);
  }
}

MethodsVector OrderSystemManager::ExportMethods() {
  MethodsVector sys_methods;
  for (auto& sys : systems_) {
    sys_methods.emplace_back(sys->system_name(), sys->ExportMethods());
  }
  return sys_methods;
}

}  // namespace coin2::exchange::base::order
